/*
 * Copyright (C) 2007-2010 Siemens AG
 *
 * This program and its interfaces are free software;
 * you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package com.siemens.ct.exi.grammar;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

import org.apache.xerces.impl.xs.models.EXIContentModelBuilder;
import org.apache.xerces.xs.StringList;
import org.apache.xerces.xs.XSAttributeDeclaration;
import org.apache.xerces.xs.XSAttributeUse;
import org.apache.xerces.xs.XSComplexTypeDefinition;
import org.apache.xerces.xs.XSConstants;
import org.apache.xerces.xs.XSElementDeclaration;
import org.apache.xerces.xs.XSNamedMap;
import org.apache.xerces.xs.XSObjectList;
import org.apache.xerces.xs.XSSimpleTypeDefinition;
import org.apache.xerces.xs.XSTypeDefinition;
import org.apache.xerces.xs.XSWildcard;

import com.siemens.ct.exi.datatype.Datatype;
import com.siemens.ct.exi.grammar.event.*;
import com.siemens.ct.exi.util.MethodsBag;

import com.siemens.ct.exi.Constants;
import com.siemens.ct.exi.exceptions.EXIException;
import com.siemens.ct.exi.grammar.rule.Rule;
import com.siemens.ct.exi.grammar.rule.SchemaInformedElement;
import com.siemens.ct.exi.grammar.rule.SchemaInformedRule;
import com.siemens.ct.exi.grammar.rule.SchemaInformedStartTag;
import com.siemens.ct.exi.types.BuiltIn;

import com.siemens.ct.exi.datatype.ListDatatype;
import com.siemens.ct.exi.datatype.EnumerationDatatype;


/**
 * TODO Description
 * 
 * @author Daniel.Peintner.EXT@siemens.com
 * @author Joerg.Heuer@siemens.com
 * 
 * @version 0.4.20090421
 */

public class XSDGrammarBuilder extends EXIContentModelBuilder {

	protected Map<QName, SchemaInformedRule> grammarTypes;

	// local-names (pre-initializing LocalName Partition)
	// uri -> localNames
	protected Map<String, List<String>> schemaLocalNames;

	// attribute wildcard namespaces
	protected List<String> atWildcardNamespaces;

	// avoids recursive element handling
	protected Set<XSElementDeclaration> handledElements;

	// pool for attribute-declaration of Attribute events
	protected Map<XSAttributeDeclaration, Attribute> attributePool;

	
    /***************************[ start of modifications ]*******************************/

    String FSM_File;            			// file name of FSM file (same as Schema file, full path, no extension)
    String FSM_Nameshort = new String("");  // same file name without path
		
    String cur_QName;            			// QName of current fragmentElement FSM
    int cur_uriID;							// URI-ID of current fragmentElement FSM
    int cur_lnID;							// LocalName-ID of current fragmentElement FSM
    
    int numberOfStates;             		// number of states in current FSM
    int stateCount = 0;             			// state counter
    
    boolean strict_mode = false;  			// strict mode info (default: false)

    char c = '/';     						// slash
    char d = '"';							// quotation mark for writing printf's
    char f = 92;							// backslash
       
	String tempuri;							// variable to temporarily store URIs	
	
	int tempuriID;							// variable to temporarily store URI-ID
	int templnID;							// variable to temporarily store LocalName-ID
	
	
	int[][][][] enum_known = new int[32][128][32][128];
	// array to remember processed enumerations, sorted after FSM QName and enumeration SchemaType QName
	// [FSM_uriID][FSM_lnID][ENUM_uriID][ENUM_lnID]
	
    
    private class State {       	// class to store state information
        String Event;           	// name of event
        String Data;            	// name of event content
        int Slots;              	// number of branches
        int[] Branches;           	// array with next states, index = event code, value = target state
        
        // additional information for ENUMs & LISTs
        
        int enum_size;				// number of ENUMERATION elements
        int enum_codelength;		// number of bits for ENUMERATION index code
        String[] enum_values;		// enumeration values
        
        String list_datatype;		// data type of LIST elements        

        protected State() {     	// constructor
            Event = new String("");
            Data = new String("");
            Slots = 0;
            Branches = new int[255];  // fixed, max. 255 branches per state
            
            enum_size = 0;
            enum_codelength = 0;
            enum_values = new String[255];
        
            list_datatype = new String("NONE");
        }
    }

    private class ValueHelper { 	// class to store strings for...
        String Datatype;        	// the according value data type to be decoded (AT & CH events)
        String Qname;				// Qname of related fragment element
        
        // additional information for ENUMs & LISTs
        
        int enum_size;				// number of ENUMERATION elements
        int enum_codelength;		// number of bits for ENUMERATION index code
        String[] enum_values;		// enumeration values
        
        String list_datatype;		// data type of LIST elements     
        
        protected ValueHelper() 	// constructor
        {
            Datatype = new String("");
            Qname = new String("");
            
            enum_size = 0;
            enum_codelength = 0;
            enum_values = new String[255];
            
            list_datatype = new String("NONE");
        }
    }

    public void set_strict_mode(boolean strict) // set strict mode on/off, called from "GrammarFactory"
    {
        strict_mode = strict;
    }

    ValueHelper[] ValueHelper_Ary = new ValueHelper[1024];  // ValueHelper_Ary array (fixed, max. 1024 functions per schema)

	protected XSDGrammarBuilder() {
		super();

		initOnce();
		
        for (int k = 0; k < 1024; k++) {
            ValueHelper_Ary[k] = new ValueHelper();        // initialize ValueHelper_Ary array
        }
        
        for (int k = 0; k < 32; k++) {
        	for (int l = 0; l < 128; l++){
        		for (int m = 0; m < 32; m++){
        			for (int n = 0; n < 128; n++){
        				enum_known[k][l][m][n] = 0;
        			}
        		}
        	}
        }
        
	}

    /***************************[ end of modifications ]*******************************/

	
	public static XSDGrammarBuilder newInstance() {
		return new XSDGrammarBuilder();
	}

	@Override
	protected void initOnce() {
		super.initOnce();

		handledElements = new HashSet<XSElementDeclaration>();
		grammarTypes = new HashMap<QName, SchemaInformedRule>();
		schemaLocalNames = new HashMap<String, List<String>>();
		atWildcardNamespaces = new ArrayList<String>();
		attributePool = new HashMap<XSAttributeDeclaration, Attribute>();
	}

	@Override
	protected void initEachRun() {
		super.initEachRun();

		handledElements.clear();
		grammarTypes.clear();
		atWildcardNamespaces.clear();
		attributePool.clear();

		schemaLocalNames.clear();
		// "", empty string
		for (String localName : Constants.LOCAL_NAMES_EMPTY) {
			addLocalNameStringEntry(XMLConstants.NULL_NS_URI, localName);
		}
		// "http://www.w3.org/XML/1998/namespace"
		for (String localName : Constants.LOCAL_NAMES_XML) {
			addLocalNameStringEntry(XMLConstants.XML_NS_URI, localName);
		}
		// "http://www.w3.org/2001/XMLSchema-instance", xsi
		for (String localName : Constants.LOCAL_NAMES_XSI) {
			addLocalNameStringEntry(
					XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, localName);
		}
		// "http://www.w3.org/2001/XMLSchema", xsd
		for (String localName : Constants.LOCAL_NAMES_XSD) {
			addLocalNameStringEntry(XMLConstants.W3C_XML_SCHEMA_NS_URI,
					localName);
		}
	}

	protected boolean isSameGrammar(List<XSElementDeclaration> elements) {
		assert (elements.size() > 1);
		for (int i = 1; i < elements.size(); i++) {
			// If all such elements have the same type name and {nillable}
			// property value, their content is evaluated according to
			// specific grammar for that element declaration
			XSElementDeclaration e0 = elements.get(0);
			XSElementDeclaration ei = elements.get(i);
			if (e0.getTypeDefinition() != ei.getTypeDefinition() || e0.getNillable() != ei.getNillable()) {
				return false;
			}
		}

		return true;
	}

	protected List<StartElement> getFragmentGrammars() {
		List<StartElement> fragmentElements = new ArrayList<StartElement>();

		// create unique qname map
		Map<QName, List<XSElementDeclaration>> namedElements = new HashMap<QName, List<XSElementDeclaration>>();
		for (XSElementDeclaration elDecl : handledElements) {
			QName en = new QName(elDecl.getNamespace(), elDecl.getName());
			if (namedElements.containsKey(en)) {
				namedElements.get(en).add(elDecl);
			} else {
				List<XSElementDeclaration> list = new ArrayList<XSElementDeclaration>();
				list.add(elDecl);
				namedElements.put(en, list);
			}
		}

        Iterator<Entry<QName, List<XSElementDeclaration>>> iter;
        iter = namedElements.entrySet().iterator();
        while (iter.hasNext()) {
			Entry<QName, List<XSElementDeclaration>> e = iter.next();
			QName qname = e.getKey();
			List<XSElementDeclaration> elements = e.getValue();
			// If there is more than one element declared with the same qname,
			// the qname is included only once.
			assert (elements.size() >= 1);
			if (elements.size() == 1) {
				// just one element for this qualified name --> simple task
				fragmentElements.add(getStartElement(elements.get(0)));
                //System.out.println(elements);
			} else {
				// multiple elements
				if (isSameGrammar(elements)) {
					fragmentElements.add(getStartElement(elements.get(0)));
				} else {
					StartElement se = new StartElement(qname);
					se.setRule(getSchemaInformedElementFragmentGrammar(elements));
					fragmentElements.add(se);
					// System.err.println("ambiguous elements " + elements);
				}
			}
		}

		return fragmentElements;
	}

	// http://www.w3.org/TR/exi/#informedElementFragGrammar
	protected Rule getSchemaInformedElementFragmentGrammar(List<XSElementDeclaration> elements) {
		// TODO 8.5.3 Schema-informed Element Fragment Grammar
		/*
		 * ElementFragmentContent
		 */
		SchemaInformedRule content = new SchemaInformedElement();
		content.addRule(START_ELEMENT_GENERIC, content); // SE (*)
		content.addTerminalRule(END_ELEMENT); // EE
		content.addRule(CHARACTERS_GENERIC, content); // CH [untyped value]
		/*
		 * ElementFragmentStartTag
		 */
		SchemaInformedRule startTag = new SchemaInformedStartTag(content);
		startTag.addRule(ATTRIBUTE_GENERIC, startTag);// AT (*)
		startTag.addRule(START_ELEMENT_GENERIC, content); // SE (*)
		startTag.addTerminalRule(END_ELEMENT); // EE
		startTag.addRule(CHARACTERS_GENERIC, content);// CH [untyped value]
		/*
		 * ElementFragmentTypeEmpty
		 */
		SchemaInformedRule typeEmpty = startTag; // not correct

		/*
		 * As with all schema informed element grammars, the schema-informed
		 * element fragment grammar is augmented with additional productions
		 * that describe events that may occur in an EXI stream, but are not
		 * explicity declared in the schema. The process for augmenting the
		 * grammar is described in 8.5.4.4 Undeclared Productions. For the
		 * purposes of this process, the schema-informed element fragment
		 * grammar is treated as though it is created from an element
		 * declaration with a {nillable} property value of true and a type
		 * declaration that has named sub-types, and ElementFragmentTypeEmpty is
		 * used to serve as the TypeEmpty of the type in the process.
		 */
		startTag.setFirstElementRule();
		startTag.setNillable(true);
		startTag.setTypeEmpty(typeEmpty);
		startTag.setTypeCastable(true);

		return startTag;
	}

	public SchemaInformedGrammar toGrammar() throws EXIException {
		if (xsModel == null || schemaParsingErrors.size() > 0) {
			StringBuffer sb = new StringBuffer("Problem occured while building XML Schema Model (XSModel)!");

			for (int i = 0; i < schemaParsingErrors.size(); i++) {
				sb.append("\n. " + schemaParsingErrors.get(i));
			}

			throw new EXIException(sb.toString());
		}
		// initialize grammars --> global elements
		List<StartElement> globalElements = initGrammars();

		// schema declared elements --> fragment grammars
		List<StartElement> fragmentElements = getFragmentGrammars();

		// sort both lists (declared & global elements)
		Collections.sort(globalElements, lexSort);
		Collections.sort(fragmentElements, lexSort);

		// schema URIs and (sorted) localNames
		String[] uris = getURITableEntries();
		GrammarURIEntry[] grammarEntries = new GrammarURIEntry[uris.length];
		for (int i = 0; i < uris.length; i++) {
			String uri = uris[i];

			// local-names
			String[] localNamesArray;
			if (schemaLocalNames.containsKey(uri)) {
				List<String> localNames = schemaLocalNames.get(uri);
				// sort local-name list
				Collections.sort(localNames);
				// create sorted array out of it
				localNamesArray = new String[localNames.size()];
				localNames.toArray(localNamesArray);
			} else {
				// no entries, may happen for XMLConstants.NULL_NS_URI
				localNamesArray = new String[0];
			}

			// prefixes
			String[] prefixes;
			if (uri.equals(XMLConstants.NULL_NS_URI)) {
				prefixes = Constants.PREFIXES_EMPTY;
			} else if (uri.equals(XMLConstants.XML_NS_URI)) {
				prefixes = Constants.PREFIXES_XML;
			} else if (uri.equals(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)) {
				prefixes = Constants.PREFIXES_XSI;
			} else if (uri.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
				prefixes = Constants.PREFIXES_XSD;
			} else {
				prefixes = new String[0];
			}

			// add schema entry
			grammarEntries[i] = new GrammarURIEntry(uri, localNamesArray, prefixes);
		}

		SchemaInformedGrammar sig = new SchemaInformedGrammar(grammarEntries, fragmentElements, globalElements);

		/*
		 * type grammar
		 */
		sig.setTypeGrammars(grammarTypes);

		/*
		 * Simple sub-type hierarchy
		 */
		Map<QName, List<QName>> subtypes = new HashMap<QName, List<QName>>();
		Iterator<QName> iterTypes = grammarTypes.keySet().iterator();
		while (iterTypes.hasNext()) {
			QName typeQName = iterTypes.next();
			XSTypeDefinition td = xsModel.getTypeDefinition(typeQName.getLocalPart(), typeQName.getNamespaceURI());
			if (td.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE && !td.getAnonymous()) {
				// XSSimpleTypeDefinition std = (XSSimpleTypeDefinition) td;
				XSTypeDefinition baseType = td.getBaseType();
				if (baseType == null) {
					// http://www.w3.org/2001/XMLSchema,anySimpleType
				} else {
					QName baseTypeQName = getQNameForType(baseType);
					List<QName> sub = subtypes.get(baseTypeQName);
					if (sub == null) {
						sub = new ArrayList<QName>();
						subtypes.put(baseTypeQName, sub);
					}
					sub.add(getQNameForType(td));
					//System.out.println( td + " instance of " + baseTypeQName);
				}
			}
		}
		sig.setSimpleTypeSubtypes(subtypes);

		/*
		 * global attributes
		 */
		XSNamedMap nm = xsModel.getComponents(XSConstants.ATTRIBUTE_DECLARATION);
		Map<QName, Attribute> globalAttributes = new HashMap<QName, Attribute>();
		for (int i = 0; i < nm.getLength(); i++) {
			XSAttributeDeclaration atDecl = (XSAttributeDeclaration) nm.item(i);
			Attribute at = getAttribute(atDecl);
			globalAttributes.put(at.getQName(), at);
		}
		sig.setGlobalAttributes(globalAttributes);
		
		
		/***************************[ start of modifications ]*******************************/
		
        int i, j;        							// counters
        Rule next;      							// variable for next element rule
        int fragments = fragmentElements.size();  	// number of fragment elements = number of FSMs
        EventInformation ei;    					// variable for current event information
        int Starttagentries;    					// number of events following element StartTag

        Writer out = null;    						// file pointer: .c
        Writer out_h = null;  						// file pointer: .h

        try
        {
            out = new FileWriter(FSM_File+".c");  				// save FSM files to schema directory
            out_h = new FileWriter(FSM_File+".h");
            
            out.write("#include "+d+FSM_Nameshort+".h"+d+"\n"); // write includes (FSM header file, decoder header file)
            out.write("#include "+d+"Processing.h"+d+"\n");
            
            out.write("\n");
            
            out.write(String.format("%s%sStrict Mode = %s\n", c, c, strict_mode));  // comment with strict mode
            
            out.write("\n");
            
            if (uris.length > 4)      // if there are additional namespaces, write URI table as comment
            {
                writeURITable(out, uris);
            }
            out.write("\n");
            
            
            out_h.write("#ifndef __"+FSM_Nameshort.toUpperCase()+"_H__\n");
            out_h.write("#define __"+FSM_Nameshort.toUpperCase()+"_H__\n");
            
            
            out_h.write("\n");
            
        } catch ( IOException e ) {
        	System.err.println( "file writer error!" );
        }

        
        // --------------- write FSM Helper function to .c file ------------------
        
        /* function to decode QNames of schema declared global elements
         * or attributes after SE(*) or AT(*) Events in EXI stream,
         * launches according FSM for next element or decoding function
         * for global attribute content
         */
        
        try {
        	out.write("//FSM Helper function generated by EXIficient\n\n");
        	
        	out.write("void FSM_Helper() {\n\n");
        	
        	
        	// get QName
        	
        	out.write("\t#ifdef DEBUGMODE\n");
        	out.write("\tprintf(" + d + f + "n" + "Decoding QName..." + f + "n" + d + ");\n");
        	out.write("\t#endif\n\n");
        	
        	out.write("\tgetQName();\n\n");  // launch QName decoding function (stores uriID & lnID in global variables)
        	
        	out.write("\t#ifdef DEBUGMODE\n");
        	out.write("\tprintf(" + d + "URI ID: %i" + f + "n" + d + ", curr_uriID);\n"); // print URI ID
        	out.write("\tprintf(" + d + "LocalName ID: %i" + f + "n" + f + "n" + d + ", curr_lnID);\n"); // print LocalName ID
        	out.write("\t#endif\n\n");
        	

        	// switch for uriID
        	out.write("\tswitch(curr_uriID)\n");
        	out.write("\t{\n");
        	
        	
        	// use EXIf. grammarEntries -> contains schema LocalNames for declared namespaces
			for (int x = 0; x < sig.grammarEntries.length; x++)
			{
				
				// level 1: uriID
				
				out.write("\t\tcase " + x + ":");	// case for uriID
				
				if (x == 0)
				{
					out.write(" " + c + c + "empty Namespace" + "\n");	// comment for empty namespace
					
					out.write("\n\t\t\t#ifdef DEBUGMODE\n");
					out.write("\t\t\tprintf(" + d + "Namespace: empty" + f + "n" + d + ");\n");	// print empty Namespace
					out.write("\t\t\t#endif\n\n");
					
				} else {
					out.write(" " + c + c + sig.grammarEntries[x].uri + "\n");	// comment for other namespaces
					
					out.write("\n\t\t\t#ifdef DEBUGMODE\n");
					out.write("\t\t\tprintf(" + d + "Namespace: " + sig.grammarEntries[x].uri + f + "n" + d + ");\n");	// print other namespaces
					out.write("\t\t\t#endif\n\n");					
				}

				// level 2: lnID
								
				out.write("\t\t\tswitch(curr_lnID)\n");
	        	out.write("\t\t\t{\n");
	        	
				for (int y = 0; y < sig.grammarEntries[x].localNames.length; y++)
				{
					out.write("\t\t\t\tcase " + y + ":");									// case for lnID
					out.write(" " + c + c + sig.grammarEntries[x].localNames[y] + "\n");	// comment for LocalName
					
					
					// search for QName in both "grammarEntries" and "globalElements" -> only global elements may occur after SE(*)
					
					boolean exists = false;
					
					for (int z = 0; z < globalElements.size(); z++)
					{
						if (globalElements.get(z) != null)
						{
							if (   (sig.grammarEntries[x].localNames[y].equals(globalElements.get(z).getQName().getLocalPart().toString()) )
								&& (sig.grammarEntries[x].uri.equals(globalElements.get(z).getQName().getNamespaceURI().toString()))  )
							{
								exists = true;
							}
						}
					}

					// QName match -> write launch for FSM and print LocalName
					
					if (exists == true)
					{
						out.write("\t\t\t\t\t#ifdef DEBUGMODE\n");
						out.write("\t\t\t\t\tprintf(" + d + "Element LocalName: " + sig.grammarEntries[x].localNames[y] + f + "n" + d + ");\n");	// print LocalName
						out.write("\t\t\t\t\t#endif\n");
						
						out.write("\t\t\t\t\tFSM_" + x + "_" + sig.grammarEntries[x].localNames[y] + "(0);\n");		// launch FSM
					}

					
					// ...not found in global element pool, look for QName in "globalAttributes"
					// -> set AT data type and launch getData() decoder function
					
					if (exists == false)
					{
						QName qname = new QName(sig.grammarEntries[x].uri, sig.grammarEntries[x].localNames[y]);
						
						if (globalAttributes.containsKey(qname))
						{	
							out.write("\t\t\t\t\t#ifdef DEBUGMODE\n");
							out.write("\t\t\t\t\tprintf(" + d + "Attribute LocalName: " + sig.grammarEntries[x].localNames[y] + f + "n" + d + ");\n");	// print LocalName
							out.write("\t\t\t\t\t#endif\n");
							
                			String datatype_str = globalAttributes.get(qname).getDatatype().getBuiltInType().toString();
                			
                			
                			// TODO: implement remaining EXI data types (FLOAT, BINARY, ...) on decoder side
                			// -> matching with Schema type names already done here
                			
                			if (datatype_str == "FLOAT" || datatype_str == "DOUBLE")
                			{
                				out.write("\t\t\t\t\tdatatype = FLOAT;\n");
                			}
                			else if (datatype_str == "BINARY_BASE64" || datatype_str == "BINARY_HEX")
                			{
                				out.write("\t\t\t\t\tdatatype = BINARY;\n");
                			}
                			else
                			{
                				// set EXIficient type name as "datatype" value in decoder (e.g. INTEGER, STRING, DECIMAL, ...)
                				out.write(String.format("\t\t\t\t\tdatatype = %s;\n", datatype_str));
                			}
                			
                			// for ENUM / LIST data types: get additional information...
                			
                			Datatype datatype = globalAttributes.get(qname).getDatatype();
                			
                			datatype_str = datatype.getClass().toString().substring(datatype.getClass().toString().lastIndexOf('.') + 1);
                			
                			// if AT data type is ENUMERATION -> need code length (number of bits to read) for ENUM index in EXI file
            				if(datatype_str.equals("EnumerationDatatype"))
            				{
            				    EnumerationDatatype myEnum = (EnumerationDatatype) datatype;
            				    int enum_size = myEnum.getEnumerationSize();
            				    int enum_codelength = myEnum.getCodingLength();
            				    
            				    String EnumQName = getName(myEnum.getSchemaType().toString(), uris, sig);
            				    int a = tempuriID;
            				    int b = templnID;
            				    
            				    // remember enumeration values in String array
            				    String[] enum_values = new String[enum_size];
            				    for(int count = 0; count < enum_size; count++)
            				    {
            				    	enum_values[count] = String.format("EN_%d_" + sig.grammarEntries[x].localNames[y].toString() + "_" + EnumQName + "_NR%d_", x, count) + myEnum.getEnumValue(count).toString();
            				    }

            				    
            				    if(enum_known[x][y][a][b] == 0)
            				    {
            				    	enum_known[x][y][a][b] = 1;
            				    	
            				    	out_h.write("\n");
        					    	
        					    	// write comment
        					    	out_h.write(String.format("%s%s ENUMERATION_" + "%d" + "_" + sig.grammarEntries[x].localNames[y].toString() + "_" + EnumQName + "\n", c, c, x));
        					    	
        					    	// write defines
        	    				    for(int count = 0; count < enum_size; count++)
        	    				    {
        	    				    	out_h.write(String.format("#define %s %d\n", getDefineString(enum_values[count]), count));
        	    				    }
        	    				    
        	    				    out_h.write("\n");
            				    }
            				    
            				    out.write(String.format("\t\t\t\t\tenum_codelength = %d;\n", enum_codelength));
            				    
                				// write call for datatype decoding function getData()
                				out.write("\t\t\t\t\tgetData();\n");
                				
            				    // write switch for enumeration values
                				out.write("\t\t\t\t\tswitch(enum_position)\n");
                				out.write("\t\t\t\t\t{\n");
            				    for(int count = 0; count < enum_size; count++)
            				    {
            				    	out.write("\t\t\t\t\t\tcase " + getDefineString(enum_values[count]) + ":\n");
            				    	
            				    	// TODO: callback
            				    	
            				        out.write("\t\t\t\t\t\t\t#ifdef DEBUGMODE\n");
            				        out.write("\t\t\t\t\t\t\tprintf(" + d + "Enumeration Value: '" + enum_values[count] + "'" + f + "n" + d + ");\n");
            				    	out.write("\t\t\t\t\t\t\t#endif\n");
            				    	            				    	
            				    	out.write("\t\t\t\t\t\t\tbreak;\n");
            				    }
            				    out.write("\t\t\t\t\t}\n");
            				}
            				
            				// if AT data type is LIST -> need data type of LIST values to launch correct decoding function
            				if(datatype_str.equals("ListDatatype"))
            				{
            				    ListDatatype myList = (ListDatatype) datatype;
            				    String list_datatype = myList.getListDatatype().toString();
            				    
            				    out.write(String.format("\t\t\t\t\tlist_datatype = %s;\n", list_datatype));
            				    
                				// write call for datatype decoding function getData()
                				out.write("\t\t\t\t\tgetData();\n");
            				}
            				
            				// if no ENUM or LIST -> just write call for getData()
            				if(!(datatype_str.equals("ListDatatype")) && !(datatype_str.equals("EnumerationDatatype")))
            				{
                				out.write("\t\t\t\t\tgetData();\n");
            				}
						}
					}
					
					out.write("\t\t\t\t\tbreak;\n\n");
				}
				
				// default case for level 2 (LocalNames)
				out.write("\t\t\t\tdefault:\n");
				// TODO: built-in grammars
				out.write("\t\t\t\t\tbreak;\n");								
				out.write("\t\t\t}\n");
				out.write("\t\t\tbreak;\n\n");
			}

			// default case for level 1 (URIs)
			out.write("\t\tdefault:\n");
			// TODO: built-in grammars
			out.write("\t\t\tbreak;\n");
			out.write("\t}\n");
			
			out.write("}\n\n");
        	        	
        }     
        catch ( IOException e1 ) {
			System.err.println( "file writer error!" );
		}
        
        // ----------------------------------------------------------------------

        
        // collect fragment elements information
        for (i = 0; i < fragments; i++)
        {
            // number of event codes after StartTag
            Starttagentries = fragmentElements.get(i).getRule().getNumberOfEvents();
            
            // name of current FSM (Namespace Index + LocalName)
            cur_QName = getName(fragmentElements.get(i).getQName().toString(), uris, sig);
            
            // remember QName IDs
            cur_uriID = tempuriID;
            cur_lnID = templnID;
            
            numberOfStates = 1;					// first state is always StartTag
            State[] States = new State[255];	// initialize FSM states array
            for (int k = 0; k < 255; k++) {		// fixed size, 255 states per FSM
                States[k] = new State();
            }
            
            // store first state
            States[0].Data = cur_QName;
            States[0].Event = getName(fragmentElements.get(i).getEventType().toString(), uris, sig);

            for (j = 0; j < Starttagentries; j++)    // run through all branches after StartTag
            {
                next = fragmentElements.get(i).getRule();     		// get next StartTag rule
                ei = fragmentElements.get(i).getRule().lookFor(j);  // branch into event code j
                next = ei.next;                               		// update "next" with next rule
                
                String Eventtype = getName(ei.event.getEventType().toString(), uris, sig);
                String Event = getName(ei.event.toString(), uris, sig);
                
                // get additional information for later decoding of ENUM / LIST data types
                
                Datatype datatype = null;
                
                int enum_size = 0;
                int enum_codelength = 0;
                String[] enum_values = new String[255];
                
                String list_datatype = new String("NONE");
                
                if (Eventtype == "ATTRIBUTE")
                {     	
                	datatype = ((Attribute) ei.event).getDatatype();
                }     
                if (Eventtype == "CHARACTERS")
                {     	
                	datatype = ((Characters) ei.event).getDatatype();
                }     
                if (Eventtype == "CHARACTERS_GENERIC")
                {     	
                	datatype = ((CharactersGeneric) ei.event).getDatatype();
                }
                                
                if (Event == "LIST" || Event.contains("LIST"))
                {
                	ListDatatype myList = (ListDatatype) datatype;
                	list_datatype = myList.getListDatatype().toString();
                }
                
                if (Event == "ENUMERATION" || Event.contains("ENUMERATION"))
                {
                	EnumerationDatatype myEnum = (EnumerationDatatype) datatype;
                	enum_size = myEnum.getEnumerationSize();
                	enum_codelength = myEnum.getCodingLength();
                	
				    String EnumQName = getName(myEnum.getSchemaType().toString(), uris, sig);
				    int a = tempuriID;
				    int b = templnID;
                	
				    // remember enumeration values in String array
				    enum_values = new String[enum_size];
				    for(int count = 0; count < enum_size; count++)
				    {
				    	enum_values[count] = String.format("EN_" + cur_QName + "_" + EnumQName + "_NR%d_", count) + myEnum.getEnumValue(count).toString();
				    }
                	

				    if(enum_known[cur_uriID][cur_lnID][a][b] == 0)
				    {
				    	try
				    	{
				    		enum_known[cur_uriID][cur_lnID][a][b] = 1;
					    	
					    	out_h.write("\n");
					    	
					    	// write comment
					    	out_h.write(String.format("%s%s ENUMERATION_" + cur_QName + "_" + EnumQName + "\n", c, c));
					    	
					    	// write defines
	    				    for(int count = 0; count < enum_size; count++)
	    				    {
	    				    	out_h.write(String.format("#define %s %d\n", getDefineString(enum_values[count]), count));
	    				    }
	    				    
	    				    out_h.write("\n");
				        }
				        catch ( IOException e1 ) {
							System.err.println( "file writer error!" );
						}
				    }
                }
                
                
                // insert information for branch j into States array
                addSlot(0, Eventtype, Event, enum_size, enum_codelength, enum_values, list_datatype, States);
                
                StateListing(next, ei, States, uris, sig, out_h);   // start "StateListing" function for recursive data collection
            }
            
           MakeMachine(States, out, out_h, uris, sig);   // write current FSM (also fills ValueHelper_Ary array for later generation of decoder function calls)
           
           ShowMachine(States);		// print current FSM to IDE console
           
           States = null;			// reset States array
        }
        try
        {
            out.write("\n");
            out_h.write("\n");
            
            // declare FSM_Helper() in header file
            out_h.write("\nvoid FSM_Helper();\n");            
            
            // write FSM_Start() function
            out.write("\n");
            out.write("void FSM_Start()\n");
            out.write("{\n");
            
            out_h.write("\n");
            out_h.write("void FSM_Start();\n");
            out_h.write("\n");
            
            
            // 1-byte buffer (global variable)
            out_h.write("unsigned char eventCode;\n");
            out_h.write("\n");
            
            
            out_h.write("\n");
            
            
            out_h.write("#endif // __"+FSM_Nameshort.toUpperCase()+"_H__\n");
            
            
            out_h.close();	// close .h file
          
            
            // ****************** update String Tables *********************
            
            /* write "insertEntry" calls into FSM_Start() to update
             * decoder String Tables with additional schema URIs and LocalNames
             * -> Strings only used in DEBUGMODE, else just counters are used! 
             */
            
            out.write("\t#ifdef DEBUGMODE\n\n");
            
            out.write("\t" + c + c + "updating String Tables..." + "\n\n");  // comment
            
        	out.write("\tString uri;\n");
        	out.write("\tunsigned int uriID;\n\n");
        	
			out.write("\tString lname;\n");
			out.write("\tunsigned int lnID;\n\n");
			
			// insert schema informed LocalNames for empty namespace
			
//			out.write("\tprintf("+d+f+"n"+"Inserting LocalNames for empty namespace"+f+"n"+f+"n"+d+");\n\n");
			
			for (int z = 0; z < sig.grammarEntries[0].localNames.length; z++)
			{
				out.write("\tlname.str = (unsigned char*)" + d + sig.grammarEntries[0].localNames[z] + d + ";\n");
				out.write("\tlname.length = (unsigned int)" + sig.grammarEntries[0].localNames[z].length() + ";\n");
				out.write("\tlnID = LocalNames_Table_insertEntry(uriTable->entries[0].lnTable, lname);\n\n");
			}
            
			// insert additional namespaces and LocalNames from schema (stored in grammarEntries[])
			
//			out.write("\tprintf("+d+f+"n"+"updating String Tables with additional Schema-informed entries"+f+"n"+f+"n"+d+");\n\n");
			
            if (sig.grammarEntries.length > 4)
            {	
            	for (int k = 4; k < sig.grammarEntries.length; k++)
            	{
            		out.write("\turi.str = (unsigned char*)" + d + sig.grammarEntries[k].uri + d + ";\n");
            		out.write("\turi.length = (unsigned int)" + sig.grammarEntries[k].uri.length() + ";\n");
            		out.write("\turiID = URI_Table_insertEntry(uriTable, uri);\n\n");
            		
            		// insert LocalNames for current namespace
    				for (int y = 0; y < sig.grammarEntries[k].localNames.length; y++)
    				{
    					out.write("\tlname.str = (unsigned char*)" + d + sig.grammarEntries[k].localNames[y] + d + ";\n");
    					out.write("\tlname.length = (unsigned int)" + sig.grammarEntries[k].localNames[y].length() + ";\n");
    					out.write("\tlnID = LocalNames_Table_insertEntry(uriTable->entries[uriID].lnTable, lname);\n\n");
    				}
            	}
            }
            
//            out.write("\tURI_Table_show();\n\n"); // print URI Table and related LocalName- and Prefix-entries

            out.write("\t#endif\n\n");
            
            // *************************************************************
            
            
            // update counters for URIs & LocalNames
            
            out.write("\t" + c + c + "update URI- & LocalName counters" + "\n\n");  // comment
            
            // update LocalName counter for empty URI
			out.write(String.format("\tlocalnamecount[0] = %d;\n\n", sig.grammarEntries[0].localNames.length));
            
			// update counters for additional URIs & LNames (uriID 5 and above)
            if (sig.grammarEntries.length > 4)
            {	
            	out.write(String.format("\turicount = %d;\n\n", sig.grammarEntries.length));
            	
            	for (int k = 4; k < sig.grammarEntries.length; k++)
            	{
            		out.write(String.format("\tlocalnamecount[%d] = %d;\n", k, sig.grammarEntries[k].localNames.length));
            	}
            }
            
            
            // set global variable strict_mode in decoder
            out.write("\n\t" + c + c + "set strict / non-strict mode" + "\n\n");  // comment
            if (strict_mode) out.write("\tset_strict_mode(1);\n\n");
            if (!strict_mode) out.write("\tset_strict_mode(0);\n\n");
            
            out.write("\t#ifdef DEBUGMODE\n");
            out.write(String.format("\tprintf("+d+"Strict Mode: %s"+f+"n"+f+"n"+d+");\n", strict_mode)); // print strict mode info
            out.write("\t#endif\n\n");
            
            out.write("\t#ifdef DEBUGMODE\n");
            out.write("\tprintf("+d+"Start Decoding EXI File..."+f+"n"+f+"n"+d+");\n");
            out.write("\t#endif\n\n");
            
            int globalElementCount = globalElements.size();      // number of possible root elements
            
            out.write("\t" + c + c + "read DocContent elements" + "\n\n"); // comment
            
            out.write("\t#ifdef DEBUGMODE\n");
            out.write(String.format("\tprintf("+d+"number of possible root elements: %d"+f+"n"+f+"n"+d+");\n", globalElementCount));
            out.write("\t#endif\n\n");
            
            out.write("\teventCode = 0;\n");	// read buffer
            
            int rootEventCodeLength = 0;                 // bit length for Event Code of root element
            
            if (strict_mode == true)
            {
            	// strict -> no undeclared elements allowed -> no SE(*) event in Document Grammar
            	
            	// TODO: is strict mode Event Code length for root element correct?
            	// -> should be log2(globalElementCount) but only works with log2(globalElementCount + 1)
            	
                rootEventCodeLength = MethodsBag.getCodingLength(globalElementCount + 1);                
//              rootEventCodeLength = MethodsBag.getCodingLength(globalElementCount);
            }
            else
            {
            	// non-strict -> undeclared elements allowed -> SE(*) event in Document Grammar
                rootEventCodeLength = MethodsBag.getCodingLength(globalElementCount + 1);
            }
            
            if (globalElementCount == 1)	// only one possible FSM after StartDocument, write FSM call
            {
                if (rootEventCodeLength != 0) out.write("\tgetbit(&eventCode, "+rootEventCodeLength+");\n");
                out.write("\tFSM_"+getName(globalElements.get(0).getQName().toString(), uris, sig)+"(0);\n");
                out.write("}\n");
                out.write("\n");
            }
            else
            {	// more than one global element -> read necessary number of bits into buffer, switch for FSM calls
                out.write("\tgetbit(&eventCode, "+rootEventCodeLength+");\n\n");
                out.write("\tswitch(eventCode)\n");
                out.write("\t{\n");
                for (i = 0; i < globalElementCount; i++)
                {
                    out.write("\t\tcase "+i+": FSM_"+getName(globalElements.get(i).getQName().toString(), uris, sig)+"(0);\n");
                    out.write("\t\t\tbreak;\n");
                    out.write("\n");
                }
                out.write("\t\tdefault:\n");
                // TODO: built-in grammars
                out.write("\t\t\tbreak;\n");
                out.write("\t}\n");
                out.write("}\n");
                out.write("\n");
            }
            out.close();        // close .c file
        }
        catch ( IOException e ) {
            System.err.println( "file writer error!" );
        }

        /***************************[ end of modifications ]*******************************/
        
        
		return sig;
	}
	
	
    /***************************[ start of modifications ]*******************************/	
	
    public void getDstFile(String xsdlocation)      	// function to get schema file name, called by "GrammarFactory"
    {
        int length = xsdlocation.length();            	// length of file name
        FSM_File = xsdlocation.substring(0, length-4); 	// remove extension, save file name with full path
        
        // save file name without path
        int lastslash = FSM_File.lastIndexOf('/');
        int lastbackslash = FSM_File.lastIndexOf(92);
        if (lastslash > lastbackslash)
        {
            FSM_Nameshort = FSM_File.substring(lastslash + 1, FSM_File.length());
        }
        else
        {
            FSM_Nameshort = xsdlocation.substring(lastbackslash + 1, FSM_File.length());
        }
    }

    // function for inserting state branches of a state into the States array
    private boolean addSlot(int StateNr, String Eventtype, String Event, int enum_size, int enum_codelength, String[] enum_values, String list_datatype, State[] States)
    {
        int slot = -1;
        int i;
        boolean a = false;
        
        for (i = 0; i < numberOfStates; i++)     // check if state already exists in FSM entries
        {
            if ((Eventtype.equals(States[i].Event)) && (Event.equals(States[i].Data)))
            {
                slot = i;                    // if exists, return array index
            }
        }
        if (slot == -1)                      // else, create new state as branch
        {
            States[numberOfStates].Event = Eventtype;
            States[numberOfStates].Data = Event;
            
            // insert ENUM / LIST info (are zero, if no ENUM / LIST)
            States[numberOfStates].enum_size = enum_size;
            States[numberOfStates].enum_codelength = enum_codelength;
            States[numberOfStates].enum_values = enum_values;
            
            States[numberOfStates].list_datatype = list_datatype;
            
            slot = numberOfStates;
            numberOfStates++;
        }
        for (i = 0; i < States[StateNr].Slots; i++) // check if branch is already inserted as slot, avoid cycles
        {
            if (States[StateNr].Branches[i] == slot)
            {
                a = true;
            }
        }
        if (a == false)       // no cycles detected, insert branch
        {
            States[StateNr].Branches[States[StateNr].Slots] = slot;
            States[StateNr].Slots++;
        }
        return a;
    }

    // function for recursive collection of state information
    private void StateListing(Rule next, EventInformation ei, State[] States, String[] uris, SchemaInformedGrammar sig, Writer out_h)
    {
    	int StateNr = -1;
    	
        String Eventtype = getName(ei.event.getEventType().toString(), uris, sig);
        String Event = getName(ei.event.toString(), uris, sig);
        
        
        // get ENUM / LIST data type information for AT and CH events
        
        Datatype datatype = null;
        
        int enum_size = 0;
        int enum_codelength = 0;
        String[] enum_values = new String[255];
        
        String list_datatype = new String("NONE");
        
        if (Eventtype == "ATTRIBUTE")
        {     	
        	datatype = ((Attribute) ei.event).getDatatype();
        }     
        if (Eventtype == "CHARACTERS")
        {     	
        	datatype = ((Characters) ei.event).getDatatype();
        }     
        if (Eventtype == "CHARACTERS_GENERIC")
        {     	
        	datatype = ((CharactersGeneric) ei.event).getDatatype();
        }

        if (Event == "LIST" || Event.contains("LIST"))
        {
        	ListDatatype myList = (ListDatatype) datatype;
        	list_datatype = myList.getListDatatype().toString();
        }
        
        if (Event == "ENUMERATION" || Event.contains("ENUMERATION"))
        {
        	EnumerationDatatype myEnum = (EnumerationDatatype) datatype;
        	enum_size = myEnum.getEnumerationSize();
        	enum_codelength = myEnum.getCodingLength();
        	
		    String EnumQName = getName(myEnum.getSchemaType().toString(), uris, sig);
		    int a = tempuriID;
		    int b = templnID;
        	
		    // remember enumeration values in String array
		    enum_values = new String[enum_size];
		    for(int count = 0; count < enum_size; count++)
		    {
		    	enum_values[count] = String.format("EN_" + cur_QName + "_" + EnumQName + "_NR%d_", count) + myEnum.getEnumValue(count).toString();
		    }
		    
		    
		    if(enum_known[cur_uriID][cur_lnID][a][b] == 0)
		    {
		    	try
		    	{
		    		enum_known[cur_uriID][cur_lnID][a][b] = 1;
		    		
			    	out_h.write("\n");
			    	
			    	// write comment
			    	out_h.write(String.format("%s%s ENUMERATION_" + cur_QName + "_" + EnumQName + "\n", c, c));
			    	
			    	// write defines
				    for(int count = 0; count < enum_size; count++)
				    {
				    	out_h.write(String.format("#define %s %d\n", getDefineString(enum_values[count]), count));
				    }
				    
				    out_h.write("\n");
		        }     
		        catch ( IOException e1 ) {
					System.err.println( "file writer error!" );
				}
		    }
		    		    
        }
        
        
        for (int j = 0; j < numberOfStates; j++)	// check if state already exists
        {
            if ((Eventtype.equals(States[j].Event)) && (Event.equals(States[j].Data)))
            {
                StateNr = j;      // if exists, save state array index
            }
        }
        
        if (StateNr == -1)        // else, create new state at index "numberOfStates"
        {
            States[numberOfStates].Event = Eventtype;
            States[numberOfStates].Data = Event;
            
            
            // store ENUM / LIST information
            States[numberOfStates].enum_size = enum_size;
            States[numberOfStates].enum_codelength = enum_codelength;
            States[numberOfStates].enum_values = enum_values;
            
            States[numberOfStates].list_datatype = list_datatype;
            
            
            StateNr = numberOfStates;
            numberOfStates++;
        }
        
        int branches = next.getNumberOfEvents();  // number of possible next events
        Rule prev = next;                         // remember parent state
        
        for (int i = 0; i < branches; i++)        // handle all branches
        {
            ei = prev.lookFor(i);                 // get event information for event code i
            next = ei.next;                       // get next rule
            
            
            // get names and data type information for States[] entry
            
            String Eventtype_next = getName(ei.event.getEventType().toString(), uris, sig);
            String Event_next = getName(ei.event.toString(), uris, sig);
            
            // get ENUM / LIST information for next state (if AT / CH event)
            
            Datatype datatype_next = null;
            
            int enum_size_next = 0;
            int enum_codelength_next = 0;
            String[] enum_values_next = new String[255];
            
            String list_datatype_next = new String("NONE");
            
            if (Eventtype_next == "ATTRIBUTE")
            {     	
            	datatype_next = ((Attribute) ei.event).getDatatype();
            }     
            if (Eventtype_next == "CHARACTERS")
            {     	
            	datatype_next = ((Characters) ei.event).getDatatype();
            }     
            if (Eventtype_next == "CHARACTERS_GENERIC")
            {     	
            	datatype_next = ((CharactersGeneric) ei.event).getDatatype();
            }
              
            if (Event_next == "LIST" || Event_next.contains("LIST"))
            {
            	ListDatatype myList = (ListDatatype) datatype_next;
            	list_datatype_next = myList.getListDatatype().toString();
            }
            
            if (Event_next == "ENUMERATION" || Event_next.contains("ENUMERATION"))
            {
            	EnumerationDatatype myEnum = (EnumerationDatatype) datatype_next;
            	enum_size_next = myEnum.getEnumerationSize();
            	enum_codelength_next = myEnum.getCodingLength();
            	
			    String EnumQName = getName(myEnum.getSchemaType().toString(), uris, sig);
			    int a = tempuriID;
			    int b = templnID;
            	
    		    // remember enumeration values in String array
    		    enum_values_next = new String[enum_size_next];
    		    for(int count = 0; count < enum_size_next; count++)
    		    {
    		    	enum_values_next[count] = String.format("EN_" + cur_QName + "_" + EnumQName + "_NR%d_", count) + myEnum.getEnumValue(count).toString();
    		    }
    		    
            	
    		    if(enum_known[cur_uriID][cur_lnID][a][b] == 0)	
    		    {
    		    	try
    		    	{
    		    		enum_known[cur_uriID][cur_lnID][a][b] = 1;
    			    	
    			    	out_h.write("\n");
    			    	
				    	// write comment
				    	out_h.write(String.format("%s%s ENUMERATION_" + cur_QName + "_" + EnumQName + "\n", c, c));
				    	
				    	// write defines
    				    for(int count = 0; count < enum_size_next; count++)
    				    {
    				    	out_h.write(String.format("#define %s %d\n", getDefineString(enum_values_next[count]), count));
    				    }
    				    
    				    out_h.write("\n");
    		        }    
    		        catch ( IOException e1 ) {
    					System.err.println( "file writer error!" );
    				}
    		    }
    		    
            }
            
            // insert state as branch for parent state
            boolean b = addSlot(StateNr, Eventtype_next, Event_next, enum_size_next, enum_codelength_next, enum_values_next, list_datatype_next, States);

            if (b == false)
            {
            	// if no cycle detected, continue recursive state listing
                StateListing(next, ei, States, uris, sig, out_h);
            }
            
        }
    }

    private void ShowMachine(State[] States)     // prints current FSM to IDE console
    {
        System.out.println();
        System.out.println("Machine: "+cur_QName);
        System.out.println();
        for (int i=0; i<numberOfStates; i++)
        {
            System.out.println("State "+i+":   "+States[i].Event+", "+States[i].Data);
            for (int j=0; j<States[i].Slots; j++)
            {
                System.out.println(j+": -->"+States[i].Branches[j]+": "+States[States[i].Branches[j]].Event+", "+States[States[i].Branches[j]].Data);
            }
        System.out.println();
        }
    }

    // function for creating FSM C code using States[] array information
    private void MakeMachine(State[] States, Writer out, Writer out_h, String[] uris, SchemaInformedGrammar sig)
    {
        int l = 0;                     	// event code length
        int strgstart, strgend;     	// substring markers for handling strings
        
        String Attr_qname = new String("");		// variable for attribute QName
        String Attr_datatype = new String("");	// variable for attribute datatype
        
        try
        {
        out.write("\n");
        out.write("void FSM_"+cur_QName+"(int state)\n");        // name for current FSM (from cur_QName)
        out_h.write("void FSM_"+cur_QName+"(int state);\n");
        out.write("{\n");
        
        out.write("\n");
        out.write("\t#ifdef DEBUGMODE\n");
        out.write("\tprintf("+d+f+"n"+"current FSM: "+cur_QName+f+"n"+d+");\n");	// print current FSM name
        out.write("\t#endif\n");
        out.write("\n");
        
        // generate update of URI- and LocalName-ID
        out.write(String.format("\tcurr_uriID = %d;\n", cur_uriID));
        out.write(String.format("\tcurr_lnID = %d;\n", cur_lnID));
        out.write("\n");
        
        out.write("\teventCode = 0;\n"); // read buffer for event code
        out.write("\n");
        
        out.write("\tswitch(state)\n");			 // switch for states
        out.write("\t{\n");
                
        for (int i = 0; i < numberOfStates; i++)			// handle all FSM states
        {
            if (States[i].Data.indexOf('{') > 0)        // if event content is QName including URI, replace with URI index
            {
                String cm = new String("");

                cm = States[i].Data.substring( States[i].Data.indexOf('(') + 1, States[i].Data.indexOf(')') );
                
                out.write("\t\tcase "+i+":\t"+c+c+States[i].Event+", "+getName(cm, uris, sig)+"\n");	// case + comment
  
                out.write("\n");
                out.write("\t\t\t#ifdef DEBUGMODE\n");
                out.write("\t\t\tprintf("+d+"Event: "+States[i].Event+f+"n"+d+");\n");			// print event name
                out.write("\t\t\tprintf("+d+"Data: "+getName(cm, uris, sig)+f+"n"+d+");\n");	// print event content
//                out.write("\t\t\tprintf("+d+"URI: "+tempuri+f+"n"+d+");\n");					// print namespace URI
                out.write("\t\t\t#endif\n");
                out.write("\n");
            }
            else
            {
                out.write("\t\tcase "+i+":\t"+c+c+States[i].Event+", "+States[i].Data+"\n");	// case + comment

                out.write("\n");
                out.write("\t\t\t#ifdef DEBUGMODE\n");
                out.write("\t\t\tprintf("+d+"Event: "+States[i].Event+f+"n"+d+");\n");			// print event name
                out.write("\t\t\tprintf("+d+"Data: "+States[i].Data+f+"n"+d+");\n");			// print event content
                out.write("\t\t\t#endif\n");
                out.write("\n");
            }

            l = MethodsBag.getCodingLength(States[i].Slots + 1);	// get event code length
            
            if (l > 0)    // code length 0 exists only for event END_ELEMENT
            {                                                 		
            	// generate decoder function calls for event content
            	
                if ((States[i].Event.equals("START_ELEMENT")) && !(States[i].Data.equals(cur_QName)))	// handle SE events
                {
                    out.write("\t\t\tFSM_"+States[i].Data+"(0);\n\n"); // jump to according element FSM
                }
            	
            	if (States[i].Event.equals("ATTRIBUTE"))      		// handle AT events
                {            		
                    strgstart = States[i].Data.indexOf('(') + 1;
                    strgend = States[i].Data.indexOf(')');
                    
                    char[] Att_qname = new char[strgend-strgstart]; 			// temp variable for attribute QName
                    
                    States[i].Data.getChars(strgstart, strgend, Att_qname, 0);	// store QName in "Att_qname"     
                    
                    for (int k = 0; k < Att_qname.length; k++)
                    {
                    	Attr_qname = Attr_qname + Att_qname[k];					// copy to String variable "Attr_qname"
                    }
                                    
                    char[] Att_data = new char[strgstart];						// temp variable for attribute datatype
                    
                    States[i].Data.getChars(0, strgstart, Att_data, 0);    		// get data type only
                    
                    for (int k = 0; k < (strgstart-1); k++)
                    {
                    	Attr_datatype = Attr_datatype + Att_data[k];
                    }
                    
                    ValueHelper_Ary[stateCount].Datatype = Attr_datatype;		// remember data type
                    ValueHelper_Ary[stateCount].Qname = cur_QName;			// remember related QName
                    
                    // remember ENUM / LIST data type information (are zero, if data type not ENUM / LIST)
                    ValueHelper_Ary[stateCount].enum_size = States[i].enum_size;
                    ValueHelper_Ary[stateCount].enum_codelength = States[i].enum_codelength;
                    ValueHelper_Ary[stateCount].enum_values = States[i].enum_values;
                    
                    ValueHelper_Ary[stateCount].list_datatype = States[i].list_datatype;
                    
                    stateCount++;

                    // reset string variables
                    Attr_qname = "";
                    Attr_datatype = "";
                    Att_qname = null;
                    Att_data = null;
                }
                
                if (States[i].Event.equals("START_ELEMENT_GENERIC"))	// handle SE(*) events for schema global elements
                {
                	// jump to FSM_Helper() function (decodes QName -> launches according element FSM)
                	out.write("\t\t\tFSM_Helper();\n\n");
                }
            	
                if (States[i].Event.equals("ATTRIBUTE_GENERIC"))	// handle AT(*) events for schema global attributes
                {
                	// jump to FSM_Helper() function (decodes QName -> sets data type and calls generic decoder function)
                	out.write("\t\t\tFSM_Helper();\n\n");
                }
                
                if (States[i].Event.equals("CHARACTERS") || States[i].Event.equals("CHARACTERS_GENERIC"))  // handle CH events
                {
                	
                    // TODO: check strict mode event code reading!
                	//		 -> even if only a single CH event is following StartTag,
                    //		 	we must read one additional 0-bit (byte) before value decoding!
                	//       -> not necessary in FSM "4_Types" (CH LIST event) in SOAP examples 9 & 10,
                	//			no additional 0-bit (byte) here -> reason?
                	
                	if ((strict_mode == true) && (States[i-1].Slots == 1) && (States[i].Data != "LIST")) // for examples 9 & 10
//                	if ((strict_mode == true) && (States[i-1].Slots == 1))
                	{
                		out.write("\t\t\t;\n");
                		out.write("\t\t\tunsigned char test = 0;\n");
                		out.write("\t\t\tgetbit(&test, 1);\n\n");
                	}
                	                	
                    // remember function name & data type
                    ValueHelper_Ary[stateCount].Datatype = States[i].Data;
                    ValueHelper_Ary[stateCount].Qname = cur_QName;
                    
                    // remember ENUM / LIST data type information (are zero, if data type not ENUM / LIST)
                    ValueHelper_Ary[stateCount].enum_size = States[i].enum_size;
                    ValueHelper_Ary[stateCount].enum_codelength = States[i].enum_codelength;
                    ValueHelper_Ary[stateCount].enum_values = States[i].enum_values;
                    
                    ValueHelper_Ary[stateCount].list_datatype = States[i].list_datatype;
                    
                    stateCount++;
                }
                
                
           
            	// generate code for decoding of all known AT / CH value data types
            	// (name and data type info already stored in ValueHelper_Ary array)                
                if (States[i].Event.equals("ATTRIBUTE") || States[i].Event.equals("CHARACTERS") || States[i].Event.equals("CHARACTERS_GENERIC"))
                {
                  
                	boolean known;	// variable for known (already implemented) data types

                	known = false;

                	// handle data types, that are already implemented in EXI Decoder
                      
                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("STRING"))
                      {               	
                    	  out.write("\t\t\tstring_value = get" + ValueHelper_Ary[stateCount-1].Datatype + "Value();\n\n");
                    	  
                    	  // TODO: callback
                    	  
                    	  known = true;
                      }
                      
                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("DATETIME"))
                      {
                          out.write("\t\t\tdate_value = get" + ValueHelper_Ary[stateCount-1].Datatype + "();\n\n");
                          
                          // TODO: callback
                          
                          known = true;
                      }
                      
                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("INTEGER"))
                      {
                      	out.write("\t\t\tint_value = get" + ValueHelper_Ary[stateCount-1].Datatype + "();\n\n");
                      	
                      	// TODO: callback
                      	
                      	known = true;
                      }
                      
                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("BOOLEAN"))
                      {
                      	out.write("\t\t\tbool_value = get" + ValueHelper_Ary[stateCount-1].Datatype + "();\n");
                      	
                      	// TODO: callback
                      	
                      	known = true;
                      }
                      
                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("UNSIGNED_LONG"))
                      {
                      	out.write("\t\t\tulong_value = getUint();\n\n");
                      	
                        out.write("\t\t\t#ifdef DEBUGMODE\n");
                      	out.write("\t\t\tprintf(" + d + f + "n" + "Unsigned Long: %u" + f + "n" + d + ", ulong_value);\n");
                        out.write("\t\t\t#endif\n\n");
                      	
                        // TODO: callback
                        
                      	known = true;
                      }
                      
                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("ENUMERATION"))
                      {
                      	out.write("\t\t\tdatatype = " + ValueHelper_Ary[stateCount-1].Datatype + ";\n");
                      	
      				    out.write(String.format("\t\t\tenum_codelength = %d;\n", ValueHelper_Ary[stateCount-1].enum_codelength));
      				    
      				    // write call for datatype decoding function getData() (decodes enum position index)
      				    out.write("\t\t\tgetData();\n");
      				                	
      				    // write switch for enumeration values
          				out.write("\t\t\tswitch(enum_position)\n");
          				out.write("\t\t\t{\n");
      				    for(int count = 0; count < ValueHelper_Ary[stateCount-1].enum_size; count++)
      				    {
      				    	out.write("\t\t\t\tcase " + getDefineString(ValueHelper_Ary[stateCount-1].enum_values[count]) + ":\n");
      				    	
      				        out.write("\t\t\t\t\t#ifdef DEBUGMODE\n");
      				        out.write("\t\t\t\t\tprintf(" + d + "Enumeration Value: '" + ValueHelper_Ary[stateCount-1].enum_values[count] + "'" + f + "n" + d + ");\n");
      				        out.write("\t\t\t\t\t#endif\n");
      				        
      				    	// TODO: callback
      				    	
      				    	out.write("\t\t\t\t\tbreak;\n");
      				    }
      				    out.write("\t\t\t}\n\n");
      				    
                      	known = true;
                      }
                      
                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("LIST"))
                      {
                    	out.write("\t\t\tdatatype = LIST_TYPE;\n");
                    	  
      				    out.write(String.format("\t\t\tlist_datatype = %s;\n", ValueHelper_Ary[stateCount-1].list_datatype));
      				    
      				    out.write("\t\t\tgetData();\n\n");
                      	
                      	known = true;
                      }
                      
                      
                      // TODO: implement remaining EXI data types on decoder side
                      
//                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("FLOAT") || ValueHelper_Ary[stateCount-1].Datatype.equals("DOUBLE"))
//                      {
//                      }
//                      
//                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("BINARY_BASE64") || ValueHelper_Ary[stateCount-1].Datatype.equals("BINARY_HEX"))
//                      {               	
//                      }               
//
//                      if (ValueHelper_Ary[stateCount-1].Datatype.equals("DECIMAL"))
//                      {               	
//                      }
                      
                      
                      if (known == false)  // yet unimplemented data type
                      {
                          out.write("\t\t\t" + c + c + "get" + ValueHelper_Ary[stateCount-1].Datatype + "();\n\n"); // write comment with name of unimplemented function
                      }
                      
                }
                
                
                
                if (strict_mode == false) l = MethodsBag.getCodingLength(States[i].Slots + 1);	// non-strict mode, get event code length
                if (strict_mode == true)  l = MethodsBag.getCodingLength(States[i].Slots);  	// strict mode, get event code length

                if ((strict_mode == false) || (States[i].Slots > 1))  // read bits for next states, if any
                {        	
                    out.write("\t\t\tgetbit(&eventCode, "+l+");\n");
                    out.write("\t\t\tswitch(eventCode)\n");             // switch for next state
                    out.write("\t\t\t{\n");
                    
                    for (int j = 0; j < States[i].Slots; j++)     // write cases
                    {
                        out.write("\t\t\t\tcase "+j+": state = "+States[i].Branches[j]+";\n");
                        out.write("\t\t\t\t\tbreak;\n");
                    }
                    
                    if (strict_mode == false)     // write default case for next states in non-strict mode
                    {
                        out.write("\t\t\t\tdefault: state = -1;\t"+c+c+"Undeclared\n");
                        out.write("\t\t\t\t\tbreak;\n");
                    }
                    out.write("\t\t\t}\n");
                }
                else
                {
                    out.write("\t\t\tstate = "+States[i].Branches[0]+";\n");  // if only one possible next state, set it
                }
                out.write("\t\t\tFSM_"+cur_QName+"(state);\n");    // launch current FSM again with next state as input
            }
            out.write("\t\t\tbreak;\n");
            out.write("\n");
        }
       
        // write FSM default case for undeclared elements / attributes
        
        out.write("\t\tdefault:\n");
        
        out.write("\t\t\t#ifdef DEBUGMODE\n");
        out.write("\t\t\tprintf("+d+"Undeclared, all stop!"+f+"n"+d+");\n");
        out.write("\t\t\t#endif\n");
        
        // TODO: built-in grammars
        
        out.write("\t\t\tbreak;\n");
        
        out.write("\t}\n");
        out.write("}\n");
        }
        catch ( IOException e ) {
        System.err.println( "file writer error!" );
        }
    }

    
    // writes URI table as comment at start of FSM file
    private void writeURITable(Writer out, String[] uris)
    {
        int i;
        try {
            out.write("\n");
            out.write("/*\tURI Table\n");
            out.write("\t---------\n");
            out.write("\n");
            for (i = 0; i < uris.length; i++)
            {
                out.write("\t"+i+":"+"\t\t"+uris[i]+"\n");
            }
            out.write("*/\n");
            out.write("\n");
        }
        catch(IOException e)
        {
            System.err.println( "print error!" );
        }
    }

    
    // removes URI from input string and replaces it with URI Table index
    private String getName(String string, String[] uris, SchemaInformedGrammar sig)
    {
        String stringConverted = new String("");
        if (string.charAt(0) == '{')                        // URI stands in {} brackets
        {
            int uriend = 0;
            
            int uriID;
            int lnID;
            
            String uriCompare = new String("");
            String localnameCompare = new String("");
            
            uriend = string.indexOf('}');
            uriCompare = string.substring(1, uriend);		// remember URI
            
            for (uriID = 0; uriID < uris.length; uriID++)
            {
                if (uriCompare.equals(uris[uriID]))         // compare URI with URI Table entries
                {
                    stringConverted = uriID + "_";          // replace with index
                    break;
                }
            }
            
            localnameCompare = string.substring(uriend+1, string.length());		// remember LocalName
            
            for (lnID = 0; lnID < sig.grammarEntries[uriID].localNames.length; lnID++)
            {
                if (localnameCompare.equals(sig.grammarEntries[uriID].localNames[lnID]))  	// compare LocalName with LocalName Table entries
                {
                    break;
                }
            }
            
            // new string: Index_LocalName
            stringConverted = stringConverted + string.substring(uriend+1, string.length());
            
            
            tempuri = new String(uriCompare);
            tempuriID = uriID;
            templnID = lnID;
            
            
        } else {
            stringConverted = string;     // if no URI, leave string untouched
        }
        return stringConverted;
    }
    
    
    private String getDefineString(String in_str)
    {
    	String out_str = in_str.toUpperCase();   	
    	out_str = out_str.replaceAll("[^A-Za-z0-9������]", "_");
    	return out_str;
    }
    
    /***************************[ end of modifications ]*******************************/


	// NOT EQUAL
	// "" [empty string],
	// "http://www.w3.org/XML/1998/namespace",
	// "http://www.w3.org/2001/XMLSchema-instance",
	// "http://www.w3.org/2001/XMLSchema"
	protected static boolean isAdditionalNamespace(String namespaceURI) {
		assert (namespaceURI != null);
		if (namespaceURI.equals(XMLConstants.NULL_NS_URI)
				|| namespaceURI.equals(XMLConstants.XML_NS_URI)
				|| namespaceURI
						.equals(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)
				|| namespaceURI.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
			return false;
		} else {
			return true;
		}
	}

	protected String[] getURITableEntries() {
		StringList namespaces = xsModel.getNamespaces();
		TreeSet<String> sortedURIs = new TreeSet<String>();

		for (int i = 0; i < namespaces.getLength(); i++) {
			String uri = namespaces.item(i) == null ? XMLConstants.NULL_NS_URI : namespaces.item(i);
			if (isAdditionalNamespace(uri)) {
				sortedURIs.add(uri);
			}
		}

		// any attribute namespaces
		for (String atWildcardURI : this.atWildcardNamespaces) {
			atWildcardURI = atWildcardURI == null ? XMLConstants.NULL_NS_URI : atWildcardURI;
			if (isAdditionalNamespace(atWildcardURI)) {
				sortedURIs.add(atWildcardURI);
			}
		}

		// copy to array (in right order)
		String[] uris = new String[4 + sortedURIs.size()];
		uris[0] = XMLConstants.NULL_NS_URI;
		uris[1] = XMLConstants.XML_NS_URI;
		uris[2] = XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI;
		uris[3] = XMLConstants.W3C_XML_SCHEMA_NS_URI;
		int compactID = 4;
		for (String addUri : sortedURIs) {
			uris[compactID] = addUri;
			compactID++;
		}

		return uris;
	}

	/*
	 * When a schema is provided, the string table (Local-name) is also
	 * pre-populated with the local name of each attribute, element and type
	 * declared in the schema, partitioned by namespace URI and sorted
	 * lexicographically.
	 */
	protected void addLocalNameStringEntry(String namespaceURI, String localName) {
		if (namespaceURI == null) {
			namespaceURI = XMLConstants.NULL_NS_URI;
		}
		// if (isNamespacesOfInterest(namespaceURI)) {
		// fetch localName list
		List<String> localNameList;
		if (schemaLocalNames.containsKey(namespaceURI)) {
			localNameList = schemaLocalNames.get(namespaceURI);
		} else {
			localNameList = new ArrayList<String>();
			schemaLocalNames.put(namespaceURI, localNameList);
		}
		// check localName value presence
		if (!localNameList.contains(localName)) {
			localNameList.add(localName);
		}
		// } else {
		// GrammarURIEntry gue = AbstractGrammar.getURIEntryForXSD();
		// boolean found = false;
		// for(String locN : gue.localNames) {
		// if(localName.equals(locN)) {
		// found = true;
		// }
		// }
		// if (!found) {
		// System.out.println("NotOfInterest: " + namespaceURI + " --> " +
		// localName);
		// }
		// }
	}

	protected List<StartElement> initGrammars() throws EXIException {
		List<StartElement> globalElements = new ArrayList<StartElement>();

		// global type definitions
		XSNamedMap types = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
		for (int i = 0; i < types.getLength(); i++) {
			XSTypeDefinition td = (XSTypeDefinition) types.item(i);

			QName name = new QName(td.getNamespace(), td.getName());
			SchemaInformedRule sir = translateTypeDefinitionToFSA(td);

			grammarTypes.put(name, sir);
		}

		// global elements
		XSNamedMap xsGlobalElements = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
		for (int i = 0; i < xsGlobalElements.getLength(); i++) {
			XSElementDeclaration globalElementDecl = (XSElementDeclaration) xsGlobalElements.item(i);

			// collect global elements (for DocContent)
			StartElement seGlobalElement = getStartElement(globalElementDecl);
			globalElements.add(seGlobalElement);
			// globalElements.add(new ExpandedName(globalElement.getNamespace(),
			// globalElement.getName()));

			// create rules for global elements (do not have scope)
			translatElementDeclarationToFSA(globalElementDecl);
		}

		// any remaining elements ? (not global elements)
		for (int i = 0; i < remainingElements.size(); i++) {
			XSElementDeclaration remElement = remainingElements.get(i);
			translatElementDeclarationToFSA(remElement);
		}

		// check entire SE pool
		// Note: copy due to ConcurrentModificationException !?
		// Iterator<XSElementDeclaration> iterSE =
		// elementPool.keySet().iterator();
		Iterator<XSElementDeclaration> iterSE = (new HashMap<XSElementDeclaration, StartElement>(elementPool)).keySet().iterator();
		while (iterSE.hasNext()) {
			XSElementDeclaration elementDecl = iterSE.next();
			StartElement se = elementPool.get(elementDecl);

			// element-rule
			SchemaInformedRule elementRule;

			XSTypeDefinition td = elementDecl.getTypeDefinition();
			if (td.getAnonymous()) {
				// create new type grammar for an anonymous type
				elementRule = translateTypeDefinitionToFSA(td);
				elementRule.setNillable(elementDecl.getNillable());
			} else {
				// fetch existing grammar from pre-processed type
				elementRule = getTypeGrammar(td.getNamespace(), td.getName());

				// *duplicate* first productions to allow different behavior
				// (e.g. property nillable is element dependent)
				if (elementDecl.getNillable()) {
					elementRule = elementRule.duplicate();
					elementRule.setNillable(true);
				} else {
					elementRule.setNillable(false);
				}
			}

			se.setRule(elementRule);
		}

		return globalElements;
	}

	protected Attribute getAttribute(XSAttributeDeclaration attrDecl)
			throws EXIException {
		// local name for string table pre-population
		addLocalNameStringEntry(attrDecl.getNamespace(), attrDecl.getName());

		Attribute at;
		if (attributePool.containsKey(attrDecl)) {
			at = attributePool.get(attrDecl);
		} else {
			// AT datatype
			XSSimpleTypeDefinition td = attrDecl.getTypeDefinition();
			QName qNameType = getQNameForType(td);
			// create new Attribute event
			QName qname = new QName(attrDecl.getNamespace(), attrDecl.getName());
			at = new Attribute(qname, qNameType, BuiltIn.getDatatype(td));
			attributePool.put(attrDecl, at);
		}

		return at;
	}

	protected QName getQNameForType(XSTypeDefinition typeDefinition) {
		QName qNameType;
		if (typeDefinition.getAnonymous()) {
			XSTypeDefinition tdBase = typeDefinition.getBaseType();
			if (tdBase.getName() == null) {
				qNameType = BuiltIn.DEFAULT_VALUE_NAME;
			} else {
				qNameType = new QName(tdBase.getNamespace(), tdBase.getName());
			}
		} else {
			qNameType = new QName(typeDefinition.getNamespace(), typeDefinition.getName());
		}
		return qNameType;
	}

	protected SchemaInformedRule handleAttributes(
			SchemaInformedRule ruleContent, SchemaInformedRule ruleContent2,
			XSObjectList attributes, XSWildcard attributeWC)
			throws EXIException {

		// Attribute Uses
		// http://www.w3.org/TR/exi/#attributeUses

		SchemaInformedRule ruleStart = new SchemaInformedStartTag(ruleContent2);
		// join top level events
		for (int i = 0; i < ruleContent.getNumberOfEvents(); i++) {
			EventInformation ei = ruleContent.lookFor(i);
			ruleStart.addRule(ei.event, ei.next);
		}

		// If an {attribute wildcard} is specified, increment n and generate an
		// additional attribute use grammar G n-1 as follows:
		// G n-1, 0 :
		// EE
		if (attributeWC != null) {
			ruleStart.addTerminalRule(END_ELEMENT);
			handleAttributeWildCard(attributeWC, ruleStart);
		}

		if (attributes != null && attributes.getLength() > 0) {
			// attributes will occur sorted lexically by qname (in EXI Stream)
			List<XSAttributeUse> vSortedAttributes = new ArrayList<XSAttributeUse>();
			for (int i = 0; i < attributes.getLength(); i++) {
				assert (attributes.item(i).getType() == XSConstants.ATTRIBUTE_USE);
				XSAttributeUse attrUse = (XSAttributeUse) attributes.item(i);
				vSortedAttributes.add(attrUse);
			}
			Collections.sort(vSortedAttributes, lexSort);

			// traverse in reverse order
			for (int i = vSortedAttributes.size() - 1; i >= 0; i--) {
				XSAttributeUse attrUse = vSortedAttributes.get(i);

				Attribute at = getAttribute(attrUse.getAttrDeclaration());

				SchemaInformedRule newCurrent = new SchemaInformedStartTag(
						ruleContent2);
				newCurrent.addRule(at, ruleStart);

				// Attribute Wildcard
				// http://www.w3.org/TR/exi/#complexTypeGrammars
				if (attributeWC != null) {
					handleAttributeWildCard(attributeWC, newCurrent);
				}

				// required attribute ?
				if (!attrUse.getRequired()) {
					// optional --> join top level events
					for (int k = 0; k < ruleStart.getNumberOfEvents(); k++) {
						EventInformation ei = ruleStart.lookFor(k);
						if (ei.event.isEventType(EventType.ATTRIBUTE_GENERIC)
								|| ei.event.isEventType(EventType.ATTRIBUTE_NS)) {
							// AT(*) & AT(uri:*) wilcards added before
						} else {
							newCurrent.addRule(ei.event, ei.next);
						}
					}
				}
				ruleStart = newCurrent;
			}
		}

		return ruleStart;

	}

	protected void handleAttributeWildCard(XSWildcard attributeWC, SchemaInformedRule rule) {

		short constraintType = attributeWC.getConstraintType();
		if (constraintType == XSWildcard.NSCONSTRAINT_ANY || constraintType == XSWildcard.NSCONSTRAINT_NOT) {
			// AT(*)
			// When the {attribute wildcard}'s {namespace
			// constraint} is any, or a pair of not and either a
			// namespace name or the special value absent indicating
			// no namespace, add the following production to each
			// grammar G i generated above:
			// G i, 0 :
			// AT(*) G i, 0
			rule.addRule(ATTRIBUTE_GENERIC, rule);
		} else {
			// AT(urix:*)
			// Otherwise, that is, when {namespace constraint} is a
			// set of values whose members are namespace names or
			// the special value absent indicating no namespace, add
			// the following production to each grammar G i
			// generated above:
			// G i, 0 :
			// AT(urix : *) G i, 0
			StringList sl = attributeWC.getNsConstraintList();
			for (int k = 0; k < sl.getLength(); k++) {
				String namespace = sl.item(k);
				rule.addRule(new AttributeNS(namespace), rule);
				// add attribute wildcard URI
				if (!atWildcardNamespaces.contains(namespace)) {
					atWildcardNamespaces.add(namespace);
				}
			}
		}
	}

	protected SchemaInformedRule getTypeGrammar(String namespaceURI, String name) {
		QName en = new QName(namespaceURI, name);
		return grammarTypes.get(en);
	}

	protected void translatElementDeclarationToFSA(
			XSElementDeclaration xsElementDeclaration) throws EXIException {

		// handle element recursion
		if (this.handledElements.contains(xsElementDeclaration)) {
			// element already handled
			return;
		}
		this.handledElements.add(xsElementDeclaration);

		// add local name entry for string table pre-population
		addLocalNameStringEntry(xsElementDeclaration.getNamespace(),
				xsElementDeclaration.getName());

		// type definition
		XSTypeDefinition td = xsElementDeclaration.getTypeDefinition();

		// type grammar
		if (td.getAnonymous()) {
			// create type grammar for anonymous type
			translateTypeDefinitionToFSA(td);
		}
	}

	public static SchemaInformedRule getUrTypeRule() {
		// ur-Type
		SchemaInformedRule urType1 = new SchemaInformedElement();
		urType1.setLabel("any");
		urType1.addRule(START_ELEMENT_GENERIC, urType1);
		urType1.addTerminalRule(END_ELEMENT);
		urType1.addRule(new CharactersGeneric(), urType1);

		SchemaInformedRule urType0 = new SchemaInformedStartTag(urType1);
		urType0.addRule(ATTRIBUTE_GENERIC, urType0);
		urType0.addRule(START_ELEMENT_GENERIC, urType1);
		urType0.addTerminalRule(END_ELEMENT);
		urType0.addRule(new CharactersGeneric(), urType1);
		urType0.setTypeCastable(true);
		urType0.setFirstElementRule();

		// empty ur-Type
		SchemaInformedRule emptyUrType0 = new SchemaInformedElement();
		emptyUrType0.addRule(ATTRIBUTE_GENERIC, emptyUrType0);
		emptyUrType0.addTerminalRule(END_ELEMENT);
		// emptyUrType0.setFirstElementRule();

		// nillable ?
		urType0.setTypeEmpty(emptyUrType0);
		urType0.setNillable(false);

		return urType0;
	}

	/**
	 * Given an XML Schema type definition T i , two type grammars are created,
	 * which are denoted by Type i and TypeEmpty i . Type i is a grammar that
	 * fully reflects the type definition of T i , whereas TypeEmpty i is a
	 * grammar that accepts only the attribute uses and attribute wildcards of T
	 * i , if any.
	 * 
	 * @param td
	 * @return
	 * @throws EXIException
	 */
	protected SchemaInformedRule translateTypeDefinitionToFSA(
			XSTypeDefinition td) throws EXIException {
		SchemaInformedRule type_i = null;
		SchemaInformedRule typeEmpty_i = null;

		// simple vs. complex type handling
		if (td.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
			if (Constants.XSD_ANY_TYPE.equals(td.getName())
					&& XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(td
							.getNamespace())) {
				// ur-type
				SchemaInformedRule urType = getUrTypeRule();
				type_i = urType;
				typeEmpty_i = urType.getTypeEmpty();
			} else {
				XSComplexTypeDefinition ctd = (XSComplexTypeDefinition) td;

				SchemaInformedRule ruleContent = translateComplexTypeDefinitionToFSA(ctd);

				// create copy of Element_i_content --> Element_i_content_2
				// (used for content schema-deviations in start-tags, direct
				// jumps)
				SchemaInformedRule ruleContent2 = ruleContent.duplicate();

				// attributes
				XSObjectList attributes = ctd.getAttributeUses();
				XSWildcard attributeWC = ctd.getAttributeWildcard();

				// type_i (start tag)
				type_i = handleAttributes(ruleContent, ruleContent2,
						attributes, attributeWC);
				type_i.setTypeCastable(isTypeCastable(ctd));

				// typeEmpty_i
				SchemaInformedRule ruleEnd = new SchemaInformedElement();
				ruleEnd.addTerminalRule(END_ELEMENT);
				typeEmpty_i = handleAttributes(ruleEnd, ruleEnd, attributes,
						attributeWC);
			}
		} else {
			assert (td.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE);
			// Type i
			XSSimpleTypeDefinition std = (XSSimpleTypeDefinition) td;
			SchemaInformedElement simpleContent = translateSimpleTypeDefinitionToFSA(std);
			type_i = handleAttributes(simpleContent, simpleContent, null, null);
			type_i.setTypeCastable(isTypeCastable(std));
			// TypeEmpty i
			SchemaInformedRule ruleEnd = new SchemaInformedElement();
			ruleEnd.addTerminalRule(END_ELEMENT);
			typeEmpty_i = handleAttributes(ruleEnd, ruleEnd, null, null);
		}

		if (!td.getAnonymous()) {
			// add to localName table for string table pre-population
			addLocalNameStringEntry(td.getNamespace(), td.getName());
		}

		type_i.setFirstElementRule();
		type_i.setTypeEmpty(typeEmpty_i);

		return type_i;
		// return new TypeGrammar(type_i, typeEmpty_i);
	}

	protected boolean isTypeCastable(XSTypeDefinition td) {

		boolean isTypeCastable = false;

		// has named sub-types
		XSNamedMap types = this.xsModel
				.getComponents(XSConstants.TYPE_DEFINITION);
		for (int i = 0; i < types.getLength(); i++) {
			XSTypeDefinition td2 = (XSTypeDefinition) types.item(i);

			if (td.equals(td2.getBaseType())) {
				isTypeCastable = true;
			}
		}

		// is a simple type definition of which {variety} is union
		if (!isTypeCastable
				&& td.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
			XSSimpleTypeDefinition std = (XSSimpleTypeDefinition) td;
			isTypeCastable = (std.getVariety() == XSSimpleTypeDefinition.VARIETY_UNION);
		}

		return isTypeCastable;
	}

	protected SchemaInformedRule translateComplexTypeDefinitionToFSA(
			XSComplexTypeDefinition ctd) throws EXIException {
		SchemaInformedRule ruleContent = null;

		switch (ctd.getContentType()) {
		case XSComplexTypeDefinition.CONTENTTYPE_EMPTY:
			// Represents an empty content type.
			// A content type with the distinguished value empty validates
			// elements
			// with no character or element information item children.
			// (attributes only, no content allowed)
			ruleContent = new SchemaInformedElement();
			ruleContent.addTerminalRule(END_ELEMENT);
			break;
		case XSComplexTypeDefinition.CONTENTTYPE_SIMPLE:
			// Represents a simple content type.
			// A content type which is simple validates elements with
			// character-only children.
			XSSimpleTypeDefinition std = ctd.getSimpleType();
			ruleContent = translateSimpleTypeDefinitionToFSA(std);
			break;
		case XSComplexTypeDefinition.CONTENTTYPE_ELEMENT:
			// Represents an element-only content type.
			// An element-only content type validates elements with children
			// that conform to the supplied content model.

			// The {content model} of a complex type definition is a single
			// particle
			boolean isMixedContent = false;
			ruleContent = handleParticle(ctd, isMixedContent);
			break;
		default:
			assert (ctd.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED);
			// Represents a mixed content type
			// The {content model} of a complex type definition is a single
			// particle
			isMixedContent = true;
			ruleContent = handleParticle(ctd, isMixedContent);

			break;
		}

		return ruleContent;

	}

	protected SchemaInformedElement translateSimpleTypeDefinitionToFSA(
			XSSimpleTypeDefinition std) throws EXIException {

		QName nameValueType;
		if (std.getAnonymous()) {
			nameValueType = new QName(null, "Anonymous");
		} else {
			nameValueType = new QName(std.getNamespace(), std.getName());
		}

		Characters chSchemaValid = new Characters(nameValueType, BuiltIn
				.getDatatype(std));

		SchemaInformedElement type_i_1 = new SchemaInformedElement();

		SchemaInformedElement type_i_0 = new SchemaInformedElement();
		type_i_0.addRule(chSchemaValid, type_i_1);

		type_i_1.addTerminalRule(END_ELEMENT);

		// TODO TypeEmpty

		return type_i_0;
	}

}
